﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace System.Windows.Controls
{
    using SharpSoft.WPF;
    using System.Collections.ObjectModel;
    using System.Windows.Media;
    using System.Windows.Media.Animation;

    /// <summary>
    /// 创建容器控件状态改变时子控件位置转换的动画效果
    /// </summary>
    public static class PanelChildrenTransitions
    {

        public class PositionState
        {
            public PositionState(DependencyObject obj)
            {
                this.Object = obj;
            }
            public DependencyObject Object { get; private set; }

            public Point Point { get; set; }

            public Size Size { get; set; }

            public Transform OldTransform { get; set; }
            public Point OldTransformOrigin { get; set; }
        }

        public class PositionStateCollection : KeyedCollection<DependencyObject, PositionState>
        {
            protected override DependencyObject GetKeyForItem(PositionState item)
            {
                return item.Object;
            }
        }

        /// <summary>
        /// 开始指定<see cref="Panel"/>的布局动画
        /// </summary>
        /// <param name="panel"><see cref="Panel"/>控件</param>
        /// <param name="update">提供一个更新容器布局的方法</param>
        /// <param name="aniDuration">动画持续时间</param>
        /// <param name="speedRatio">动画速度比例</param>
        public static void LayoutAnimation(this Panel panel, Action update, TimeSpan? aniDuration = null, double speedRatio = 1.5d)
        {
            var win = VisualHelper.FindParent<Window>(panel);
            var panelPoint1 = panel.TranslatePoint(new Point(), win);//记录容器位置
            var states = panel.SnapState();
            EventHandler callback = null;
            callback = (S, E) =>
            {
                panel.LayoutUpdated -= callback;


                var panelPoint2 = panel.TranslatePoint(new Point(), win);
                var sb = panel.CreateStoryboard(states, aniDuration, speedRatio);

                var v = panelPoint1 - panelPoint2;//计算容器的偏移

                if (v.Length != 0d)
                { //******容器偏移动画******
                    TranslateTransform translateTransform = new TranslateTransform(0d, 0d);
                    panel.RenderTransformOrigin = new Point(0D, 0D);
                    panel.RenderTransform = translateTransform;
                    DoubleAnimation dax = new DoubleAnimation() { From = v.X, To = 0 };
                    Storyboard.SetTarget(dax, panel);
                    Storyboard.SetTargetProperty(dax, new PropertyPath("RenderTransform.(TranslateTransform.X)"));
                    DoubleAnimation day = new DoubleAnimation() { From = v.Y, To = 0 };
                    Storyboard.SetTarget(day, panel);
                    Storyboard.SetTargetProperty(day, new PropertyPath("RenderTransform.(TranslateTransform.Y)"));
                    if (aniDuration.HasValue)
                    {
                        dax.Duration = aniDuration.Value;
                        day.Duration = aniDuration.Value;
                    }
                    dax.SpeedRatio = speedRatio;
                    day.SpeedRatio = speedRatio;

                    sb.Children.Add(dax);
                    sb.Children.Add(day);
                }


                sb.Begin(panel, HandoffBehavior.SnapshotAndReplace);
            };
            if (update != null)
            {
                panel.LayoutUpdated += callback;
                panel.BeginInit();
                try
                {
                    update.Invoke();
                }
                catch (Exception)
                {
                    throw;
                }
                finally
                {
                    panel.EndInit();
                    panel.UpdateLayout();//确保LayoutUpdated事件至少会执行一次
                }

            }
        }

        /// <summary>
        /// 开始指定容器和其子UI（可以是多级子UI）的布局动画
        /// </summary>
        /// <param name="container">布局容器</param>
        /// <param name="children">子项</param>
        /// <param name="update">提供一个更新容器布局的方法</param>
        /// <param name="aniDuration">动画持续时间</param>
        /// <param name="speedRatio">动画速度比例</param>
        public static void LayoutAnimation(this FrameworkElement container, IEnumerable<UIElement> children, Action update, TimeSpan? aniDuration = null, double speedRatio = 1.5d)
        { 
            var win = VisualHelper.FindParent<Window>(container);
            var panelPoint1 = container.TranslatePoint(new Point(), win);//记录容器位置
            var states = SnapState(container, children);
            EventHandler callback = null;
            callback = (S, E) =>
            {
                container.LayoutUpdated -= callback;


                var panelPoint2 = container.TranslatePoint(new Point(), win);
                var sb = CreateStoryboard(container, children, states, aniDuration, speedRatio);

                var v = panelPoint1 - panelPoint2;//计算容器的偏移

                if (v.Length != 0d)
                { //******容器偏移动画******
                    TranslateTransform translateTransform = new TranslateTransform(0d, 0d);
                    container.RenderTransformOrigin = new Point(0D, 0D);
                    container.RenderTransform = translateTransform;
                    DoubleAnimation dax = new DoubleAnimation() { From = v.X, To = 0 };
                    Storyboard.SetTarget(dax, container);
                    Storyboard.SetTargetProperty(dax, new PropertyPath("RenderTransform.(TranslateTransform.X)"));
                    DoubleAnimation day = new DoubleAnimation() { From = v.Y, To = 0 };
                    Storyboard.SetTarget(day, container);
                    Storyboard.SetTargetProperty(day, new PropertyPath("RenderTransform.(TranslateTransform.Y)"));
                    if (aniDuration.HasValue)
                    {
                        dax.Duration = aniDuration.Value;
                        day.Duration = aniDuration.Value;
                    }
                    dax.SpeedRatio = speedRatio;
                    day.SpeedRatio = speedRatio;

                    sb.Children.Add(dax);
                    sb.Children.Add(day);
                }


                sb.Begin(container, HandoffBehavior.SnapshotAndReplace);
            };
            if (update != null)
            {
                container.LayoutUpdated += callback;
                try
                {
                    update.Invoke();
                }
                catch (Exception)
                {
                    throw;
                }
                finally
                {
                    container.UpdateLayout();//确保LayoutUpdated事件至少会执行一次
                }

            }
        }


        /// <summary>
        /// 快照当前状态
        /// </summary>
        /// <param name="panel"></param>
        /// <returns></returns>
        private static PositionStateCollection SnapState(this Panel panel)
        {
            Point zero = new Point(0d, 0d);
            PositionStateCollection coll = new PositionStateCollection();
            foreach (var item in panel.Children)
            {
                if (item is UIElement ele)
                {
                    var p = ele.TranslatePoint(zero, panel);
                    PositionState state = new PositionState(ele) { Point = p, Size = ele.RenderSize };

                    coll.Add(state);
                }
            }
            return coll;
        }
        private static PositionStateCollection SnapState(FrameworkElement container, IEnumerable<UIElement> elements)
        {
            Point zero = new Point(0d, 0d);
            PositionStateCollection coll = new PositionStateCollection();
            foreach (var item in elements)
            {
                if (item is UIElement ele)
                {
                    var p = ele.TranslatePoint(zero, container);
                    PositionState state = new PositionState(ele) { Point = p, Size = ele.RenderSize };

                    coll.Add(state);
                }
            }
            return coll;
        }
        /// <summary>
        /// 创建从记录的状态到当前状态的动画
        /// </summary>
        /// <param name="panel"></param>
        /// <param name="states">一般使用<see cref="SnapState"/>创建子控件的状态。</param>
        /// <param name="dur">动画持续时长</param>
        /// <returns></returns>
        private static Storyboard CreateStoryboard(this Panel panel, PositionStateCollection states, TimeSpan? dur = null, double speedRatio = 1.5d)
        {
            Point zero = new Point(0d, 0d);
            Storyboard sb = new Storyboard() { FillBehavior = FillBehavior.Stop, };
            foreach (var item in panel.Children)
            {
                if (item is UIElement ele)
                {
                    if (states.Contains(ele))
                    {
                        var state = states[ele];
                        state.OldTransformOrigin = ele.RenderTransformOrigin;
                        state.OldTransform = ele.RenderTransform;
                        var p = ele.TranslatePoint(zero, panel);
                        var anis = CreateAniTo(ele, state, p);

                        foreach (var ani in anis)
                        {
                            if (dur.HasValue)
                            {
                                ani.Duration = new Duration(dur.Value);
                            }
                            ani.IsAdditive = false;
                            ani.IsCumulative = false;
                            ani.SpeedRatio = speedRatio;
                            sb.Children.Add(ani);
                        }

                    }
                }
            }
            return sb;
        }
        private static Storyboard CreateStoryboard(FrameworkElement container, IEnumerable<UIElement> elements, PositionStateCollection states, TimeSpan? dur = null, double speedRatio = 1.5d)
        {
            Point zero = new Point(0d, 0d);
            Storyboard sb = new Storyboard() { FillBehavior = FillBehavior.Stop, };
            foreach (var ele in elements)
            {
                if (states.Contains(ele))
                {
                    var state = states[ele];
                    state.OldTransformOrigin = ele.RenderTransformOrigin;
                    state.OldTransform = ele.RenderTransform;
                    var p = ele.TranslatePoint(zero, container);
                    var anis = CreateAniTo(ele, state, p);

                    foreach (var ani in anis)
                    {
                        if (dur.HasValue)
                        {
                            ani.Duration = new Duration(dur.Value);
                        }
                        ani.IsAdditive = false;
                        ani.IsCumulative = false;
                        ani.SpeedRatio = speedRatio;
                        sb.Children.Add(ani);
                    }

                }
            }
            return sb;
        }
        /// <summary>
        /// 创建移动到目标的动画
        /// </summary>
        /// <param name="ele"></param>
        /// <param name="state"></param>
        /// <param name="curpoint"></param>
        /// <returns></returns>
        private static DoubleAnimation[] CreateAniTo(UIElement ele, PositionState state, Point curpoint)
        {
            var s = ele.RenderSize;
            var v = state.Point - curpoint;
            TransformGroup group = new TransformGroup();
            TranslateTransform translateTransform = new TranslateTransform(0d, 0d);
            ele.RenderTransformOrigin = new Point(0D, 0D);
            ele.RenderTransform = group;
            DoubleAnimation dax = new DoubleAnimation() { From = v.X, To = 0 };
            Storyboard.SetTarget(dax, ele);
            Storyboard.SetTargetProperty(dax, new PropertyPath("RenderTransform.(TransformGroup.Children)[1].(TranslateTransform.X)"));
            DoubleAnimation day = new DoubleAnimation() { From = v.Y, To = 0 };
            Storyboard.SetTarget(day, ele);
            Storyboard.SetTargetProperty(day, new PropertyPath("RenderTransform.(TransformGroup.Children)[1].(TranslateTransform.Y)"));

            List<DoubleAnimation> list = new List<DoubleAnimation>() { dax, day };
            Vector scale = new Vector(1, 1);
            if (s.Width > 0 && s.Height > 0)
            {//避免除数为0
                scale = new Vector(state.Size.Width / s.Width, state.Size.Height / s.Height);
            }
            ScaleTransform scaleTransform = new ScaleTransform(1, 1);
            group.Children.Add(scaleTransform);
            group.Children.Add(translateTransform);

            DoubleAnimation daW = new DoubleAnimation() { From = scale.X, To = 1 };
            Storyboard.SetTarget(daW, ele);
            Storyboard.SetTargetProperty(daW, new PropertyPath("RenderTransform.(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"));
            DoubleAnimation daH = new DoubleAnimation() { From = scale.Y, To = 1 };
            Storyboard.SetTarget(daH, ele);
            Storyboard.SetTargetProperty(daH, new PropertyPath("RenderTransform.(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"));

            list.Add(daW);
            list.Add(daH);
            return list.ToArray();
        }
    }
}
